package edu.ntu.cloudroid.server.backendservice;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.google.appengine.api.datastore.Key;

import edu.ntu.cloudroid.model.PMF;
import edu.ntu.cloudroid.model.data.AbstractData;
import edu.ntu.cloudroid.model.friend.FriendData;
import edu.ntu.cloudroid.model.friend.FriendInfo;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.nongui.util.TimeCounter;
import edu.ntu.cloudroid.server.RequestServlet;
import edu.ntu.cloudroid.server.UploadServlet;
import edu.ntu.cloudroid.server.backendservice.util.CloudDroidCache;

/**
 * @author ttadinh
 * Adding and retrieving friends
 */
public class FriendService {
	private static CloudDroidCache<String, List<FriendData>> cache = new CloudDroidCache<String, List<FriendData>>(1000);
	private static CloudDroidCache<String, FriendData> friendRelationCache = new CloudDroidCache<String, FriendData>(1000);
	private static final String DEFAULT_DATE="2011-01-01 00:00";
	static Map<DelayedUpdateKey, Date> delayedUpdates = new HashMap<DelayedUpdateKey, Date>();
	static Object lock = new Object();
	static TimeCounter counter =new TimeCounter("friend insert", UploadServlet.logger);		
	/**
	 * This is called before updating friend list
	 */
	public static void clearFriendList(String user) throws Exception{
		deleteAllFriendsForUser(user);		
	}
	
	public static void purgeCache(){
		 cache = new CloudDroidCache<String, List<FriendData>>(1000);
		 friendRelationCache = new CloudDroidCache<String, FriendData>(1000);

	}
	public static void addFriend(FriendInfo fi) throws Exception{
		FriendData fd = new FriendData();
		fd.setUserName(fi.getUser1());
		fd.setFriendName(fi.getUser2());
		SimpleDateFormat sdf = new SimpleDateFormat(
				"yyyy-MM-dd HH:mm");
		sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
		Date date = sdf.parse(FriendService.DEFAULT_DATE);
		fd.setFriendTimestamp(date);
		counter.start(true);
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try{			
			pm.currentTransaction().begin();			
			pm.makePersistent(fd);
			pm.currentTransaction().commit();			
		}
		catch (Exception e){
			e.printStackTrace();
		}
		pm.close();
		counter.end(true);
		
		counter.start(true);
		fd = new FriendData();
		fd.setUserName(fi.getUser2());
		fd.setFriendName(fi.getUser1());
		fd.setFriendTimestamp(date);
		pm = PMF.get().getPersistenceManager();
		try{			
			pm.currentTransaction().begin();			
			pm.makePersistent(fd);
			pm.currentTransaction().commit();			
		}
		catch (Exception e){
			e.printStackTrace();
		}
		pm.close();
		counter.end(true);
	}
		
	public static void deleteAllFriends() throws Exception{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		/*pm.getFetchPlan().addGroup(AbstractData.FETCH_GROUP_NAME);
		pm.getFetchPlan().setMaxFetchDepth(2);*/	
		List<AbstractData> returnList = new ArrayList<AbstractData>();
		long start = System.currentTimeMillis();
		try {
			Query query = pm.newQuery(FriendData.class);
			
			@SuppressWarnings("unchecked")
			List<FriendData> result = (List<FriendData>) query.execute();
			
			for (FriendData v : result){
				pm.deletePersistent(v);
			}			
			//query.execute();
			
			long end = System.currentTimeMillis();
			RequestServlet.logger.info("friend delete time = "+(end-start));
		} finally {
			pm.close();		
		}
	}
	
	public static void deleteAllFriendsForUser(String personName) throws Exception{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		/*pm.getFetchPlan().addGroup(AbstractData.FETCH_GROUP_NAME);
		pm.getFetchPlan().setMaxFetchDepth(2);*/	
		List<AbstractData> returnList = new ArrayList<AbstractData>();
		long start = System.currentTimeMillis();
		try {
			Query query = pm.newQuery(FriendData.class, "userName==personName");
			query.declareParameters("String personName");
			@SuppressWarnings("unchecked")
			List<FriendData> result = (List<FriendData>) query.execute(personName);
			
			for (FriendData v : result){
				pm.deletePersistent(v);
			}			
			//query.execute();
			
			long end = System.currentTimeMillis();
			UploadServlet.logger.info("friend delete time = "+(end-start));
		} finally {
			pm.close();		
		}
	}
	
	public static void removeFriend(String user, String friend){
		PersistenceManager pm = PMF.get().getPersistenceManager();
		/*pm.getFetchPlan().addGroup(AbstractData.FETCH_GROUP_NAME);
		pm.getFetchPlan().setMaxFetchDepth(2);*/	
		List<AbstractData> returnList = new ArrayList<AbstractData>();
		long start = System.currentTimeMillis();
		try {
			Query query = pm.newQuery(FriendData.class, "userName==user && friendName==friend");
			query.declareParameters("String user, String friend");
			@SuppressWarnings("unchecked")
			List<FriendData> result = (List<FriendData>) query.execute(user, friend);
			
			for (FriendData v : result){
				pm.deletePersistent(v);
			}			
			//query.execute();
			
			long end = System.currentTimeMillis();
			UploadServlet.logger.info("friend delete time = "+(end-start));
		} finally {
			pm.close();		
		}				
	}
		
	//querying the FriendInfo datastore
	public static List<FriendData> getFriendList(String user, PersistenceManager pm) throws Exception{	
		List<FriendData> p = cache.get(user);
		if (p==null){
			//querying the friend datastore
			Query query = pm.newQuery(FriendData.class, "userName==user");
			query.declareParameters("String user");
			@SuppressWarnings("unchecked")
			List<FriendData> result = (List<FriendData>) query.execute(user);
			List<FriendData> friends = new ArrayList<FriendData>();
			for (FriendData v : result){
				friends.add(pm.detachCopy(v));
			}								
			cache.put(user, friends);
			return friends;		
		}
		else
			return p;
	}
	
	public static List<ProfileData> getFriendsProfile(String user) throws Exception{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		List<FriendData> friends = getFriendList(user,pm);			
		List<ProfileData> returnedList = new ArrayList<ProfileData>();
		for (FriendData friend: friends)
			returnedList.add(ProfileService.getProfile(friend.getFriendName()));
		pm.close();
		return returnedList;		
	}
			
	public static FriendData getFriendRelation(String user1, String user2) throws Exception{	
		PersistenceManager pm = PMF.get().getPersistenceManager();		
		FriendData friend=friendRelationCache.get(user1+","+user2);
		if (friend==null){
		try {
			System.out.println("Querying friend");
			Query query = pm.newQuery(FriendData.class, "userName==user1 && friendName==user2");
			query.declareParameters("String user1, String user2");
			@SuppressWarnings("unchecked")
			List<FriendData> result = (List<FriendData>) query.execute(user1, user2);
				if (result.size() > 0) {
					friend = pm.detachCopy(result.get(0));
					friendRelationCache.put(user1 + "," + user2, friend);
				}
		} finally {
			pm.close();		
		}	
		}
		return friend;		
	}
	
	public static Date getFriendTimeStamp(String user, String friend)
			throws Exception {
		FriendData fr = getFriendRelation(user,friend);
		if (fr==null)
			return null;
		else
			return fr.getFriendTimestamp();
	}
	
	public static void updateFriendTimeStamp(String user, String friend) throws Exception{
		synchronized (lock) {									
			Date ts = delayedUpdates.get(new DelayedUpdateKey(user, friend));
			if (ts == null) 								
				return;
			
			FriendData fr = getFriendRelation(user,friend);
			if (fr==null) return;
			fr.setFriendTimestamp(ts);
			
			PersistenceManager pm = PMF.get().getPersistenceManager();
			try {
				pm.currentTransaction().begin();				
				pm.makePersistent(fr);
				pm.currentTransaction().commit();
			} catch (Exception e) {
				System.out.println("Error updating friend timpstamp");
				e.printStackTrace();
			} finally {
				if (pm.currentTransaction().isActive()) {
					pm.currentTransaction().rollback();
				}
				pm.close();
			}
		}
	}

	public static void delayTimestampUpdate(String person, String friend, Date ts){
		synchronized (lock) {
			DelayedUpdateKey duk = new DelayedUpdateKey(person, friend);
			// System.out.println("*** Delaying timestamp update "+ts+" preson = "+person+" vs "+friend);
			if (!delayedUpdates.containsKey(duk))
				delayedUpdates.put(duk, ts);

			if (ts.after(delayedUpdates.get(duk))) {
				delayedUpdates.remove(duk);
				delayedUpdates.put(duk, ts);
			}
		}
	}
}

class DelayedUpdateKey{
	String userName, friendName;
	
	public DelayedUpdateKey(String un, String fn){
		this.userName = un;
		this.friendName =fn;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((friendName == null) ? 0 : friendName.hashCode());
		result = prime * result
				+ ((userName == null) ? 0 : userName.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DelayedUpdateKey other = (DelayedUpdateKey) obj;
		if (friendName == null) {
			if (other.friendName != null)
				return false;
		} else if (!friendName.equals(other.friendName))
			return false;
		if (userName == null) {
			if (other.userName != null)
				return false;
		} else if (!userName.equals(other.userName))
			return false;
		return true;
	}
	
}
